/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package MyBot.reactive;

import cz.cuni.pogamut.Client.*;
import cz.cuni.pogamut.MessageObjects.*;
import cz.cuni.pogamut.introspection.PogProp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Ivan Medeiros Monteiro
 */
public class DummyBehavior extends ReactiveBehavior {
    /** choosen item for the state seeItem */
    protected Item m_choosenItem = null;
    /** choose med kits for the stateMedKit */
    protected ArrayList<Item> m_choosenMedKits = null;
    /** is used to store shuffled list of weapons bot runs around */
    protected ArrayList<Item> m_itemsToRunAround = null;
    /** last m_enemy which disappeared from agent's view */
    protected Player m_lastEnemy = null;
    /** the m_enemy we're fixed at. If null no m_enemy is engaged. */
    protected Player m_enemy = null;
    /** max attempts in reaching the item */
    protected final int MAX_ATTEMPTS = 10;
    /**
     * Stores last unreachable item - item that bot chose and was not able to go to. <br>
     * This setting should prevent bot from stucks.
     */
    protected Item m_previousChoosenItem = null;
    /** counter for attempts in reaching the item */
    protected int m_walkingAttempts = 0;
    /** walking mystic properties - prevent bot from continuous jumping - he will jump only once */
    protected boolean m_jumped;
    /** use built-in A* algorithm or not use built-in A* algorithm? that is a question */
    @PogProp
    public boolean m_useAStar = false;
    /*******
     * AGENT SWITCHES!!!
     *******/
    /** boolean switch to activate engage */
    @PogProp
    public boolean m_shouldEngage = true;
    /** boolean switch to activate pursue */
    @PogProp
    public boolean m_shouldPursue = true;
    /** boolean switch to activate rearm */
    @PogProp
    public boolean m_shouldRearm = true;
    /** boolean switch to activate collect items */
    @PogProp
    public boolean m_shouldCollectItems = true;
    /** boolean switch to activate collect health */
    @PogProp
    public boolean m_shouldCollectHealth = true;
    /** how low the health level should be to start collecting healht */
    @PogProp
    public int m_healthLevel = 90;
    /** how many bot the hunter killed */
    @PogProp
    public int m_frags = 0;
    /** how many times the hunter died */
    @PogProp
    public int m_deaths = 0;


    public DummyBehavior(AgentMemory memory, AgentBody body, GameMap gameMap , Logger log, Logger plataformLog){
        super(memory, body, gameMap, log, plataformLog);
        
    }


    @Override
    public void doLogic() {
        // IF-THEN RULES:
        // 1) see m_enemy and has better weapon? -> switch to better weapon
        if (this.m_shouldRearm && m_memory.getSeeAnyEnemy() && this.hasBetterWeapon()) {
            this.stateChangeToBetterWeapon();
            return;
        }

        // 2) do you see m_enemy? 	-> go to PURSUE (start shooting / hunt the m_enemy)
        if (this.m_shouldEngage && m_memory.getSeeAnyEnemy() && m_memory.hasAnyLoadedWeapon()) {
            this.stateEngage();
            return;
        }

        // 3) are you shooting? 	-> go to STOP_SHOOTING (stop shooting, you've lost your target)
        if (m_memory.isShooting()) {
            this.stateStopShooting();
            return;
        }

        // 4) are you being shot? 	-> go to HIT (turn around - try to find your m_enemy)
        if (m_memory.isBeingDamaged()) {
            this.stateHit();
            return;
        }

        // 5) have you got m_enemy to pursue? -> go to the last position of m_enemy
        if ((this.m_lastEnemy != null) && (this.m_shouldPursue) && (m_memory.hasAnyLoadedWeapon())) {
            this.stateGoAtLastEnemyPosition();
            return;
        }
        // 6) are you walking? 	   	-> go to WALKING       (check WAL)
        if (m_memory.isColliding()) {
            this.stateWalking();
            return;
        }

        // 7) do you see item? 		-> go to GRAB_ITEM	  (pick the most suitable item and run for)
        if (this.m_shouldCollectItems && this.seeAnyReachableItemAndWantIt()) {
            this.stateSeeItem();
            return;
        }

        // 8) are you hurt?			-> get yourself some medKit
        if (m_memory.getAgentHealth() < this.m_healthLevel && this.canRunAlongMedKit()) {
            this.stateMedKit();
            return;
        }

        // 9) run around items
        this.stateRunAroundItems();
        return;
    }

        /**
     * changes to better weapon that he posseses
     */
    protected void stateChangeToBetterWeapon() {
        m_log.log(Level.INFO, "Decision is: CHANGE WEAPON");
        if (m_memory.getAgentLocation() == null || m_memory.getSeeEnemy() == null || m_memory.getSeeEnemy().location == null) {
            return;
        }
        AddWeapon weapon = m_memory.getBetterWeapon(m_memory.getAgentLocation(), m_memory.getSeeEnemy().location);
        if (weapon != null) {
            m_body.changeWeapon(weapon);
        }
    }

    /**
     * has better weapon - this magic check goes through weapons in inventory and according to their characteristics
     * decides which is the best - that means which effectiveDistance is lowest and which maximal distance is big enough
     * to reach m_enemy.
     * </p>
     * <p>
     * Note!: Both effective and maximal distance are guessed and therefore could not work exactly
     * </p>
     */
    protected boolean hasBetterWeapon() {
        Player enemy = m_memory.getSeeEnemy();
        if (m_memory.getAgentLocation() == null || enemy == null || enemy.location == null) {
            return false;
        }
        AddWeapon weapon = m_memory.getBetterWeapon(m_memory.getAgentLocation(), enemy.location);
        // m_platformLog.info("Better weapon : " + weapon + "\nWeapons: " + this.memory.getAllWeapons().toString());
        if (weapon == null) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * Fired when bot see any m_enemy.
     * <ol>
     * <li> if have enemyID - checks whether the same m_enemy is visible, if not, drop him (and stop shooting)
     * <li> if doesn't have enemyID - pick one of the m_enemy for pursuing
     * <li> if not shooting at enemyID - start shooting
     * <li> if out of ammo - switch to another weapon
     * <li> if m_enemy is reachable - run to him
     * <li> if m_enemy is not reachable - stand still (kind a silly, right? :-)
     * </ol>
     */
    protected void stateEngage() {
        m_log.log(Level.INFO, "Decision is: ENGAGE");
        // 1) if have enemyID - checks whether the same m_enemy is visible, if not, drop ID (and stop shooting)
        if (this.m_enemy != null) {
            this.m_lastEnemy = m_enemy;
            this.m_enemy = m_memory.getSeePlayer(this.m_enemy.ID); // refresh information about the m_enemy,
            // note that even though we've got pointer to the message of the m_enemy seen, it's still a certain message
            // from a specific time - when new message arrives it's written as a new message
            if (this.m_enemy == null) {
                if (m_memory.isShooting()) {
                    m_body.stopShoot();
                } // stop shooting, we've lost target
                return;
            }
        }

        // 2) if doesn't have m_enemy - pick one of the m_enemy for pursuing
        if (this.m_enemy == null) {
            this.m_enemy = m_memory.getSeeEnemy();
            if (this.m_enemy == null) {
                m_body.stop();
                m_body.stopShoot();
                return;
            }
        }

        AddWeapon weapon = null;
        // 3) if out of ammo - switch to another weapon
        if ((!m_memory.hasLoadedWeapon()) && m_memory.hasAnyLoadedWeapon()) {
            m_platformLog.info("no ammo - switching weapon " + m_memory.hasLoadedWeapon() + " " + m_memory.getAnyWeapon() + "\nCurrent Weapon:" + m_memory.getCurrentWeapon() + "\nWeapons : " + m_memory.getAllWeapons().toString());
            weapon = m_memory.getAnyWeapon();
            if ((weapon != null) && ((m_memory.getCurrentWeapon() == null) || ((m_memory.getCurrentWeapon() != null) && (!weapon.weaponType.equals(m_memory.getCurrentWeapon().weaponType))))) {
                m_platformLog.info("no ammo - switching weapon: " + weapon);
                m_body.changeWeapon(weapon);
            } else {

            }
        }

        // 4) if not shooting at enemyID - start shooting
        double distance = (Triple.distanceInSpace(m_memory.getAgentLocation(), this.m_enemy.location));
        if (m_memory.getCurrentWeapon() != null && m_memory.getCurrentWeapon().maxDist > distance) {// it is worth shooting
            m_platformLog.info("Would like to shoot at enemy!!!");
            if (!m_memory.isShooting()) {
                m_body.shoot(this.m_enemy);
            } else // to turn to m_enemy - shoot will not turn to m_enemy during shooting
            {
                m_body.turnToTarget(this.m_enemy);
            }
        }

        // 5) if m_enemy is far - run to him
        int decentDistance = Math.round(m_random.nextFloat() * 800) + 200;

        if (m_memory.getAgentLocation() != null && this.m_enemy != null && this.m_enemy.location != null &&
                Triple.distanceInSpace(m_memory.getAgentLocation(), m_enemy.location) < decentDistance) {
            if (m_memory.isMoving()) {
                m_body.stop();
            }
        } else {
            m_body.runToTarget(m_enemy);
            m_jumped = false;
        }
    }

    /**
     * Fired when bot loose m_enemy from his view <br>
     * He just stops shooting and no more wastes his ammo
     */
    protected void stateStopShooting() {
        m_log.log(Level.INFO, "Decision is: STOP_SHOOTING");
        m_body.stopShoot();
    }

    /**
     * Fired when bot is damaged, it has those options:
     * <ol>
     * <li> He has idea where to turn to from to DAM message
     * <li> He got no idea at all -> turns around
     * </ol>
     */
    protected void stateHit() {
        this.m_log.log(Level.INFO, "Decision is: HIT");
        m_body.turnHorizontal(55);
    }

    /**
     * Fired when bot is moving, checks few accidents than can happen to him
     * <ol>
     * <li> Wall collision
     * <li> Fell of the bot
     * <li> Bump to another actor of the game
     * </ol>
     */
    protected void stateWalking() {
        m_log.log(Level.INFO, "Decision is: WALKING");

        if (m_memory.isColliding()) {
            if (!m_jumped) {
                m_body.doubleJump();
                m_jumped = true;
            } else {
                m_body.stop();
                m_jumped = false;
            }
        }
        if (m_memory.isFalling()) {
            m_body.sendGlobalMessage("I am flying like a bird:D!");
            m_log.info("I'm flying like an angel to the sky ... it's so high ...");
        }
        if (m_memory.isBumpingToAnotherActor()) {
            m_body.stop();
        }
    }

    /**
     * State pursue is for pursuing m_enemy who was for example lost behind a corner.
     * How it works?:
     * <ol>
     * <li> initialize properties
     * <li> obtain path to the m_enemy
     * <li> follow the path - if it reaches the end - set m_lastEnemy to null - bot would have seen him before or lost him once for all
     * </ol>
     */
    protected void stateGoAtLastEnemyPosition() {
        m_log.log(Level.INFO, "Decision is: PURSUE");
        if (!m_gameMap.safeRunToLocation(m_lastEnemy.location)) {         // unable to reach the choosen item
            m_log.info("Ended at the enemy possition or failed - > STOP THE CHASE.");
            m_previousChoosenItem = m_choosenItem;
            m_lastEnemy = null;
        }
        return;
    }

    /**
     * checks whether there are any medkit items around and if there are
     * checks if the agent is not standing on the first one in the m_choosenMedKits
     * <p>
     * (bot got stucked because nearestHealth returns Healths according to inventory spots
     * not to the current situation, so the bot with low health got stucked on the inventory spot)
     * <p>
     * @return true if bot can run along med kits - initialize them before that
     */
    protected boolean canRunAlongMedKit() {
        if (this.m_choosenMedKits == null) {
            this.m_choosenMedKits = m_gameMap.nearestHealth(4, 8);
        }
        // no medkits to run to around the agent - restricted AStar - see nearestHealth
        if (m_choosenMedKits.isEmpty()) {
            this.m_choosenMedKits = null;
            return false;
        }
        // bot is too close to the object - possibly standing at the only one
        if (Triple.distanceInSpace(m_choosenMedKits.get(0).location, m_memory.getAgentLocation()) < 15) {
            // there are many - remove the first one - seeItem has highest priority, so bot should
            // pick up the item anyway and otherwise will not get stucked at the inventory spot of
            // the item
            if (m_choosenMedKits.size() > 2) {
                m_choosenMedKits.remove(0);
            } else {
                this.m_choosenItem = null;
                return false;
            }
        }
        return true;
    }

    /**
     * runs along healths of strenght at least 8 to recover health
     */
    protected void stateMedKit() {
        this.m_log.log(Level.INFO, "Decision is: RUN_MED_KITS");
        m_gameMap.runAroundItemsInTheMap(m_choosenMedKits, this.m_useAStar);
    }

    /**
     * choose weapon according to the one he is currently holding
     * <ol>
     * <li> has melee and see ranged => pick up ranged
     * <li> has ranged and see melee => pick up melee
     * <li> pick up first weapon he sees
     * </ol>
     *
     * @return the choosen one weapon
     */
    private Weapon chooseWeapon() {
        ArrayList<Weapon> weapons = m_memory.getSeeReachableWeapons();
        for (Weapon weapon : weapons) {
            // 0) has no weapon in hands
            if (m_memory.getCurrentWeapon() == null) {
                return weapon;
            }
            // 1) weapon is ranged, bot has melee
            if ((m_memory.getCurrentWeapon().melee) && !weapon.isMelee() && !m_memory.hasWeaponOfType(weapon.weaponType)) {
                return weapon;
            }
            // 2) weapon is melee, bot has ranged
            if (!m_memory.getCurrentWeapon().melee && weapon.isMelee() && !m_memory.hasWeaponOfType(weapon.weaponType)) {
                return weapon;
            }
        }
        Weapon chosen = m_memory.getSeeReachableWeapon();
        if (!m_memory.hasWeaponOfType(chosen.weaponType)) {
            return chosen;
        }
        return null;
    }

    /**
     * Reasoning about what to do with seen item <br>
     * the easiest way of handeling it will be just to take it every time, but what should we do
     * when there are many of items laying in front of agent?
     * <ol>
     * <li> choose weapon - choose the type he is lacking (melee/ranged)
     * <li> choose armor
     * <li> choose health - if the health is bellow normal maximum
     * <li> choose ammo - if it is suitable for possessed weapons
     * <li> ignore the item
     * </ol>
     */
    private Item chooseItem() {
        // 1) choose weapon - choose the type he is lacking (melee/ranged)
        if (m_memory.getSeeAnyReachableWeapon()) {
            return chooseWeapon();
        }
        // 2) choose armor
        if (m_memory.getSeeAnyReachableArmor()) {
            return m_memory.getSeeReachableArmor();
        }
        // 3) choose health - if the health is bellow normal maximum or the item is boostable
        if (m_memory.getSeeAnyReachableHealth()) {
            Health health = m_memory.getSeeReachableHealth();
            if (m_memory.getAgentHealth() < 100) {
                return health;
            }
            if (health.boostable) // if the health item is boostable, grab it anyway:)
            {
                return health;
            }
        }
        // 4) choose ammo - if it is suitable for possessed weapons
        if ((m_memory.getSeeAnyReachableAmmo()) &&
                (m_memory.isAmmoSuitable(m_memory.getSeeReachableAmmo()))) {
            return m_memory.getSeeReachableAmmo();
        }
        // 5) ignore the item
        return null;
    }

    /**
     * sees reachable item and wants it
     * @return true if there is an item which is useful for agent
     */
    private boolean seeAnyReachableItemAndWantIt() {
        if (m_memory.getSeeAnyReachableItem()) {
            m_choosenItem = chooseItem();
            if (m_choosenItem != null) {
                m_log.info("NEW ITEM CHOSEN: " + m_choosenItem);
                m_log.info("LAST CHOOSEN ITEM: " + m_previousChoosenItem);
            }
        } else {
            m_choosenItem = null;
        }
        if ((m_choosenItem != null) && (!m_choosenItem.equals(m_previousChoosenItem))) //&& (Triple.distanceInSpace(memory.getAgentLocation(), m_choosenItem.location) > 20))
        {
            return true;
        } else {
            return false;
        }
    }

    /**
     * run along the path to choosen item
     */
    protected void stateSeeItem() {
        m_log.log(Level.INFO, "Decision is: SEE_ITEM --- Running for: " + this.m_choosenItem.toString());
        if (!m_gameMap.safeRunToLocation(m_choosenItem.location)) {         // unable to reach the choosen item
            m_log.info("unable to REACH the choosen item");
            m_previousChoosenItem = m_choosenItem;
            m_choosenItem = null;
        }
        this.m_jumped = false;
    }

    /**
     * call predefined function in GameMap - runAroundWeaponsInTheMap
     */
    protected void stateRunAroundItems() {
        m_log.log(Level.INFO, "Decision is: RUN_AROUND_ITEMS");
        m_gameMap.runAroundItemsInTheMap(m_itemsToRunAround, m_useAStar);
    }

    

    @Override
    public void receiveMessage(RcvMsgEvent e) {
                // Take care of m_frags and m_deaths.
        switch (e.getMessage().type) {
            case PLAYER_KILLED:
                m_frags += 1;
                break;
            case BOT_KILLED:
                m_deaths += 1;
                break;
        }
    }


    @Override
    public void postPrepareAgent() {
        this.m_itemsToRunAround = new ArrayList<Item>();
        for (Item item : m_memory.getKnownWeapons()) {
            this.m_itemsToRunAround.add(item);
        }
        for (Item item : m_memory.getKnownArmors()) {
            this.m_itemsToRunAround.add(item);
        }
        Collections.shuffle(m_itemsToRunAround);
    }
}
